/*
 * Project: data-synchronize-post
 *
 * File Created at 2017年3月9日
 *
 * Copyright 2016 zt Corporation Limited.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * ZYHY Company. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license.
 */
package com.zt.bigdata.util;


import org.apache.http.Consts;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.NoHttpResponseException;
import org.apache.http.auth.AuthSchemeProvider;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.ConnectionConfig;
import org.apache.http.config.Lookup;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContexts;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.BasicSchemeFactory;
import org.apache.http.impl.auth.DigestSchemeFactory;
import org.apache.http.impl.auth.NTLMSchemeFactory;
import org.apache.http.impl.auth.SPNegoSchemeFactory;
import org.apache.http.impl.auth.KerberosSchemeFactory;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.nio.conn.NoopIOSessionStrategy;
import org.apache.http.nio.conn.SchemeIOSessionStrategy;
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
import org.apache.http.nio.reactor.ConnectingIOReactor;
import org.apache.http.nio.reactor.IOReactorException;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.Serializable;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.nio.charset.CodingErrorAction;
import java.util.Map;

/**
 * @Type HttpClientUtils2.java
 * @Desc http client 工具类
 * @author wjr
 * @date 2017年3月9日 上午11:27:12
 * @version
 */
@SuppressWarnings("deprecation")
public class HttpClientUtils2 implements Serializable {

    private static final Logger log = LoggerFactory.getLogger(HttpClientUtils2.class);
    private static final int TIME_OUT = 30 * 1000;
    private static final int RETRY_TIME = 1;
    private static CloseableHttpClient httpClient = null;
    private static CloseableHttpAsyncClient asyncHttpClient = null;

    /**
     * 配置http request
     * @param request
     */
    private static void config(HttpRequestBase request) {
        request.addHeader("Authorization", "Basic QWxpY2U6MTIzNDU2");
        request.setHeader("Accept", "application/json");
        request.setHeader("Content-type", "application/json;charset=utf-8");
        RequestConfig requestConfig = RequestConfig.custom()
                .setConnectionRequestTimeout(TIME_OUT)
                .setConnectTimeout(TIME_OUT)
                .setSocketTimeout(TIME_OUT)
                .build();
        request.setConfig(requestConfig);
    }

    /**
     * @return
     * @throws IOReactorException
     */
    public static CloseableHttpAsyncClient getAsyncHttpClient() throws IOReactorException {
        if (asyncHttpClient == null) {
            asyncHttpClient = createAsyncHttpClient();
        }
        if (!asyncHttpClient.isRunning()) {
            asyncHttpClient.start();
        }
        return asyncHttpClient;
    }

    /**
     * @return
     * @throws IOReactorException
     */
    public static CloseableHttpAsyncClient createAsyncHttpClient() throws IOReactorException {
        SSLContext sslcontext = SSLContexts.createDefault();
        Registry<SchemeIOSessionStrategy> sessionStrategyRegistry = RegistryBuilder
                .<SchemeIOSessionStrategy> create()
                .register("http", NoopIOSessionStrategy.INSTANCE)
                .register("https", new SSLIOSessionStrategy(sslcontext))
                .build();
        IOReactorConfig ioReactorConfig = IOReactorConfig
                .custom()
                .setIoThreadCount(Runtime.getRuntime().availableProcessors())
                .build();
        ConnectingIOReactor ioReactor = new DefaultConnectingIOReactor(ioReactorConfig);
        PoolingNHttpClientConnectionManager pm = new PoolingNHttpClientConnectionManager(
                ioReactor, null, sessionStrategyRegistry, null);
        pm.setMaxTotal(100);
        pm.setDefaultMaxPerRoute(50);
        ConnectionConfig connectionConfig = ConnectionConfig
                .custom()
                .setMalformedInputAction(CodingErrorAction.IGNORE)
                .setUnmappableInputAction(CodingErrorAction.IGNORE)
                .setCharset(Consts.UTF_8)
                .build();
        pm.setDefaultConnectionConfig(connectionConfig);
        Lookup<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder
                .<AuthSchemeProvider> create()
                .register(AuthSchemes.BASIC, new BasicSchemeFactory())
                .register(AuthSchemes.DIGEST, new DigestSchemeFactory())
                .register(AuthSchemes.NTLM, new NTLMSchemeFactory())
                .register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory())
                .register(AuthSchemes.KERBEROS, new KerberosSchemeFactory())
                .build();
        CloseableHttpAsyncClient client = HttpAsyncClients
                .custom()
                .setConnectionManager(pm)
                .setDefaultAuthSchemeRegistry(authSchemeRegistry)
                .setDefaultCookieStore(new BasicCookieStore())
                .build();
        return client;
    }


    /**
     * 自定义header
     * @param url
     * @param data
     * @param headers
     * @return
     */
    public static String asyncPostUseHeader(String url, String data, Map<String, String>
            headers) {
        HttpPost httpPost = new HttpPost(url);
        for(String key : headers.keySet()) {
            httpPost.setHeader(key, headers.get(key));
        }
        return asyncPost(httpPost, data);
    }


    /**
     * @param url
     * @param data
     * @return
     */
    public static String asyncPost(String url, String data) {
//        FutureCallback<HttpResponse> callback = null;
        HttpPost httpPost = new HttpPost(url);
        return asyncPost(httpPost, data);
    }

    /**
     * @param httpPost
     * @param data
     * @return
     */
    public static String asyncPost(HttpPost httpPost, String data) {
//        FutureCallback<HttpResponse> callback = null;
        config(httpPost);
//        Future<HttpResponse> response = null;
        String result = null;
        try {
            StringEntity se = new StringEntity(data);
            httpPost.setEntity(se);
//            response = getAsyncHttpClient().execute(httpPost, callback);
            getAsyncHttpClient().execute(httpPost, null);
        } catch(Exception e) {
            log.error(e.getMessage(), e);
        }
        return result;
    }

    /**
     * 获取 http client方法
     * @return http client
     */
    public static CloseableHttpClient getHttpClient() {
        if (httpClient == null) {
            httpClient = createHttpClient();
        }
        return httpClient;
    }

    /**
     * 以池的方式创建http 连接，设置http连接重试机制
     * @return http连接
     */
    private static CloseableHttpClient createHttpClient() {
        ConnectionSocketFactory plainCsf = PlainConnectionSocketFactory.getSocketFactory();
        LayeredConnectionSocketFactory sslCsf = SSLConnectionSocketFactory.getSocketFactory();
        Registry<ConnectionSocketFactory> registry = RegistryBuilder
                .<ConnectionSocketFactory> create()
                .register("http", plainCsf)
                .register("https", sslCsf)
                .build();
        PoolingHttpClientConnectionManager pm = new PoolingHttpClientConnectionManager(registry);
        pm.setMaxTotal(100);
        pm.setDefaultMaxPerRoute(50);

        HttpRequestRetryHandler httpRequestRetryHandler = new HttpRequestRetryHandler() {
            @Override
            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
                //重试3次，放弃
                if (executionCount >= RETRY_TIME) {
                    return false;
                }
                //服务器丢失连接，重试
                if (exception instanceof NoHttpResponseException) {
                    return true;
                }
                //SSL握手异常，放弃
                if (exception instanceof SSLHandshakeException) {
                    return false;
                }
                //超时，放弃
                if (exception instanceof InterruptedIOException) {
                    return false;
                }
                //目标服务器不可达
                if (exception instanceof UnknownHostException) {
                    return false;
                }
                //连接被拒绝
                if (exception instanceof ConnectTimeoutException) {
                    return false;
                }
                //ssl握手异常
                if (exception instanceof SSLException) {
                    return false;
                }
                HttpClientContext clientContext = HttpClientContext.adapt(context);
                HttpRequest request = clientContext.getRequest();
                // 如果请求是幂等的，就再次尝试
                if (!(request instanceof HttpEntityEnclosingRequest)) {
                    return true;
                }
                return false;
            }

        };
        CloseableHttpClient client = HttpClients
                .custom()
                .setConnectionManager(pm)
                .setRetryHandler(httpRequestRetryHandler)
                .build();
        return client;
    }

    /**
     * 自定义header, 发送同步请求
     *
     * @param url
     * @param data
     * @param headers
     * @return
     */
    public static String postUseHeader(String url, String data, Map<String, String>
            headers) {
        HttpPost httpPost = new HttpPost(url);
        for (String key : headers.keySet()) {
            httpPost.setHeader(key, headers.get(key));
        }
        return post(httpPost, data);
    }

    /**
     * 封装http post方法
     * @param url 远程接口url
     * @param data post发送的数据
     * @return
     */
    public static String post(String url, String data) {
        HttpPost httpPost = new HttpPost(url);
//        httpPost.addHeader("Authorization", "Basic QWxpY2U6MTIzNDU2");
        return post(httpPost, data);
    }

    /**
     * 发送http请求
     * @param httpPost
     * @param data
     * @return
     */
    public static String post(HttpPost httpPost, String data) {
        config(httpPost);
        CloseableHttpResponse response = null;
        String result = null;
        try {
            StringEntity se = new StringEntity(data);
            httpPost.setEntity(se);
            response = getHttpClient().execute(httpPost);
            if (response.getStatusLine().getStatusCode() != 200) {
                log.warn("post failed: get code:[{}], data:[{}]", response.getStatusLine().getStatusCode(), data);
            }
        } catch(Exception e) {
            log.error(e.getMessage(), e, data);
        } finally {
            try {
                if(response != null) {
                    response.close();
                }
            } catch(IOException e) {
                log.error(e.getMessage(), e);
            }
        }
        return result;
    }

    /**
     * @param url
     * @return
     */
    public static String doGet(String url) {
        HttpGet httpGet = new HttpGet(url);
        config(httpGet);
        CloseableHttpResponse httpResponse = null;
        StringBuffer result = new StringBuffer();
        try {
            httpResponse = getHttpClient().execute(httpGet);
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    httpResponse.getEntity().getContent()));
            String inputLine;
            while ((inputLine = reader.readLine()) != null) {
                result.append(inputLine);
            }
            reader.close();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            try {
                if(httpResponse != null) {
                    httpResponse.close();
                }
            } catch(IOException e) {
                log.error(e.getMessage(), e);
            }
        }
        return result.toString();
    }

    /**
     * SSL访问
     *
     * @param url
     * @return
     * @throws Exception
     */
    public static String getSSLRequest(String url) throws Exception {
        URL u = new URL(url);
        StringBuffer result = new StringBuffer();
        URLConnection conn = null;
        if ("https".equalsIgnoreCase(u.getProtocol())) {
            SslUtils.ignoreSsl();
        }
        try {
            conn = u.openConnection();
            conn.setConnectTimeout(TIME_OUT);
            conn.setReadTimeout(TIME_OUT);
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    conn.getInputStream()));
            String inputLine;
            while ((inputLine = reader.readLine()) != null) {
                result.append(inputLine);
            }
            reader.close();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {

        }
        return result.toString();
    }


}


/**
 * Revision history
 * -------------------------------------------------------------------------
 *
 * Date Author Note
 * -------------------------------------------------------------------------
 * 2017年3月9日 wjr creat
 */
